/*
 * LowLevel.cpp
 *
 *  Created on: Jul 8, 2009
 *      Author: yanir
 */

#include "LowLevel.h"

LowLevel::LowLevel() {
        _pointer_size = sizeof(void*);
        _char_size = sizeof(char);

        _first_additional_fBlocks = 0;
        _first_additional_iNods = 0;

        _additional_Fb = 0;
        _additional_iNodsb = 0;

        bool opened = false;
        int check = _fd = open("FILE_SYS", O_RDWR , 0666);
        if (check <= 0){
                  perror ("Open FILE_SYS failed in Low level.\n");
                    switch (errno) {
                      case EACCES:  perror ("Permission denied.\n"); break;
                      case EMFILE:  perror ("No file handle available.\n"); break;
                      case ENOENT:  perror ("File or path not found.\n"); break;
                      default:      perror ("Unknown error.\n"); break;
                    }
        }else{
                opened = true;
        }

        if (opened){
                int offset = 0;

                check = lseek(_fd,4,SEEK_SET);
                if (check < 0){
                        perror("seek failed at low level constructor (1)");
                }else{
                        read(_fd,&offset,_pointer_size);
                        _block_size = offset;

                }

                check = lseek(_fd,24,SEEK_SET);
                if (check < 0){
                                        perror("seek failed at low level constructor (2)");
                }else{
                        read(_fd,&offset,_pointer_size);
                        _inodes_num= offset;

                }

                check = lseek(_fd,12,SEEK_SET);
                if (check < 0){
                         perror("seek failed at low level constructor (3)");
                }else{
                         read(_fd,&offset,_pointer_size);
                         _free_blocks_Num= offset;

                }
                check = lseek(_fd,0,SEEK_SET);
                if (check < 0){
                                        perror("seek failed at low level constructor (4)");
                }else{
                        read(_fd,&offset,_pointer_size);
                        _dataDiskSize = offset;

                }

                calculate_additional_block();
                set_additionals();

        }



}

LowLevel::~LowLevel() {
        // TODO Auto-generated destructor stub
}

/**  allocates a new i-node and returns its number **/
int LowLevel::allocateInode()
{

        int iNumber = 0;
        //check if there is a space to add a node
        int check = lseek(_fd,28,SEEK_SET);
        if(check < 0){
                        perror("seek failed at allocation i-node (1) (low level)");

        }
        read(_fd,&_inodes_num,_pointer_size);
        if(_inodes_num == 0){
                return -1;   // -1 = cannot perform allocation
        }

        check = lseek(_fd,36,SEEK_SET);
        if(check < 0){
                perror("seek failed at allocation i-node (2) (low level)");

        }

        int position = 0;


        read(_fd,&position,_pointer_size);
        position -= _pointer_size;              //now position = the address of the last free node address in node list

        if(position >= _first_additional_iNods && _first_additional_iNods !=0){
                        if(position == _first_additional_iNods){
                                iNumber = handle_iNode_aloc(position);
                                position = (_block_size*2) - _pointer_size;
                                write(_fd,&position,_pointer_size);     //update the last free i node pointer
                        }else if( ((position-_first_additional_iNods)% _block_size) == 0 ){
                                        iNumber = handle_iNode_aloc(position);
                                        position -= _pointer_size;
                                        write(_fd,&position,_pointer_size);     //update the last free i node pointer

                        }else{
                                iNumber = handle_iNode_aloc(position);
                                write(_fd,&position,_pointer_size);     //update the last free i node pointer
                        }


        }else{

                        iNumber = handle_iNode_aloc(position);
                        write(_fd,&position,_pointer_size);     //update the last free i node pointer
        }

        iNumber = get_inode_position(iNumber);

        int freeInods = 0;
        //update free i nodes number
        lseek(_fd,28,SEEK_SET);
        read(_fd,&freeInods,_pointer_size);
        freeInods--;
        lseek(_fd,28,SEEK_SET);
        write(_fd,&freeInods,_pointer_size);

        setInodeType(iNumber,'x');

        return iNumber;

}



int LowLevel::handle_iNode_aloc(int position)
{
        int noVal = 0;
        int iNumber = 0;
        int iNode = 0;
        lseek(_fd,position,SEEK_SET);  // move fp to position
        read(_fd,&iNode,_pointer_size); //read the address value into iNode
        iNumber = iNode;  //get the i node physical address
        lseek(_fd,position,SEEK_SET);
        write(_fd,&noVal,_pointer_size);  //remove the new allocated i node from the free i node list
        lseek(_fd,36,SEEK_SET); //**
        return iNumber;
}

int LowLevel::freeInode(int i_node)
{

        int iNodPhys = get_inode_physcal_address(i_node);


        //////////// CHECK CONDITIONS /////////////////////

        //check if there are nodes to remove
        int check = lseek(_fd,28,SEEK_SET);
        if(check < 0){
                        perror("seek failed at free i-node (1) (low level)");

        }
        int free_iNods = 0;
        read(_fd,&free_iNods,_pointer_size);

        check = lseek(_fd,24,SEEK_SET);
        if(check < 0){
                        perror("seek failed at free i-node (2) (low level)");

        }
        int totalInodes = 0;
        read(_fd,&totalInodes,_pointer_size);

        //check if there are no i nodes to free
        if(free_iNods == totalInodes){
                perror("there are no i nodes to free ");
                return -1;   // -1 = all the i nodes are free
        }

        //check if the i_node is out of bound
        if( (i_node >  totalInodes ) || (i_node < 0)){
                perror("i node cannot be freed , i node out of bound ");
                return -1;   // -1 = all the i nodes are free
        }


        //check if the i nodes has more then 1 hard link
        if (get_hardLinks_num(i_node) > 1){
                perror("i node cannot be freed , hard links count > 1");
                return -1;
        }

        //check if the i node is allocated by the type filed
        if (getInodeType(i_node) == 0){
                perror("i node is already free");
                return -1;
        }


        /////////// UPDATE I NODES ADDRESS LIST //////////////



        check = lseek(_fd,36,SEEK_SET); //start of free i nodes list
        if(check < 0){
                perror("seek failed at free i-node (3) (low level) ");

        }
        int position = 0;
        read(_fd,&position,_pointer_size);// position = (blank)free address in free i nodes list
        if(_first_additional_iNods !=0){
        if(position == ((_block_size*2) - _pointer_size)){

                handle_iNodeFree(position,iNodPhys);
                position = _first_additional_iNods;
                write(_fd,&position,_pointer_size);     //update the last free i node pointer

        }else if( (((position-_first_additional_iNods) +2*_pointer_size )  % _block_size) == 0 ){

                        handle_iNodeFree(position,iNodPhys);
                        position += _pointer_size;
                        write(_fd,&position,_pointer_size);     //update the last free i node pointer

        }else{

                handle_iNodeFree(position,iNodPhys);
                position += _pointer_size;
                write(_fd,&position,_pointer_size);     //update the last free i node pointer
        }


        }else{

                        handle_iNodeFree(position,iNodPhys);
                        position += _pointer_size;
                        write(_fd,&position,_pointer_size);     //update the last free i node pointer
        }


        //////////////// DELETE RELATED BLOCKS //////////////////////

        int blocksNum = 0;
        double temp = ((double)getFileSize(i_node)/(double)_block_size) + 1;
        blocksNum = temp;
        if (getFileSize(i_node)%_block_size == 0 ){
                blocksNum--;
        }
        int i = 0;
        while (i < blocksNum){
                freeDataBlock(getDataBlock(i_node,i));
                i++;
        }

        ///delete i node
        char noVal = 0;
		lseek(_fd,iNodPhys,SEEK_SET);
		for (int i = 0 ; i < _block_size ; i++){
				write(_fd,&noVal,_char_size);
		}

		//update free i nodes number
		int freeInods = 0;
		lseek(_fd,28,SEEK_SET);
		read(_fd,&freeInods,_pointer_size);
		freeInods++;
		lseek(_fd,28,SEEK_SET);
		write(_fd,&freeInods,_pointer_size);

        return 0; // yekshemaj . great success.




}


void LowLevel::handle_iNodeFree(int position,int iNode_address)
{

                lseek(_fd,position,SEEK_SET);  // move fp to position
                write(_fd,&iNode_address,_pointer_size); //write the address of the free i node to the end of the list
                lseek(_fd,36,SEEK_SET); //**

}


int LowLevel::allocateDataBlock()
{
        int iNumber = 0;
                //check if there is a space to add a node
                int check = lseek(_fd,12,SEEK_SET);
                if(check < 0){
                                perror("seek failed at allocation block (1) (low level)");

                }
                read(_fd,&_free_blocks_Num,_pointer_size);
                if(_free_blocks_Num == 0){
						perror("no availbe blocks to allocate");
                        return -1;   // -1 = cannot perform allocation
                }

                check = lseek(_fd,20,SEEK_SET);
                if(check < 0){
                        perror("seek failed at allocation block (2) (low level)");

                }

                int position = 0;


                read(_fd,&position,_pointer_size);
                position -= _pointer_size;              //now position = the address of the last free block address in node list

                if(position >= _first_additional_fBlocks && _first_additional_fBlocks !=0){
                                if(position == _first_additional_fBlocks){
                                        iNumber = handle_block_aloc(position);
                                        position = (_block_size*3) - _pointer_size;
                                        write(_fd,&position,_pointer_size);     //update the last free block pointer

                                }else if( ((position-_first_additional_fBlocks)% _block_size) == 0 ){
                                                iNumber = handle_block_aloc(position);
                                                position -= _pointer_size;
                                                write(_fd,&position,_pointer_size);     //update the last free block pointer


                                }else{
                                        iNumber = handle_block_aloc(position);
                                        write(_fd,&position,_pointer_size);     //update the last free block pointer

                                }


                }else{

                                iNumber = handle_block_aloc(position);
                                write(_fd,&position,_pointer_size);     //update the last free block pointer

                }



                int freeBlocks = 0;
                //update free i nodes number
                lseek(_fd,12,SEEK_SET);
                read(_fd,&freeBlocks,_pointer_size);
                freeBlocks--;
                lseek(_fd,12,SEEK_SET);
                write(_fd,&freeBlocks,_pointer_size);


                return iNumber;

}


int LowLevel::handle_block_aloc(int position)
{
        int noVal = 0;
                int iNumber = 0;
                int block = 0;
                lseek(_fd,position,SEEK_SET);  // move fp to position
                read(_fd,&block,_pointer_size); //read the address value into iNode
                iNumber = block;  //get the i node physical address
                lseek(_fd,position,SEEK_SET);
                write(_fd,&noVal,_pointer_size);  //remove the new allocated i node from the free i node list
                lseek(_fd,20,SEEK_SET); //**
                return iNumber;
}


int LowLevel::freeDataBlock(int dblock)
{
        //////////// CHECK CONDITIONS /////////////////////

                //check if there are blocks to remove
                int check = lseek(_fd,12,SEEK_SET);
                if(check < 0){
                                perror("seek failed at free i-node (1) (low level)");

                }
                read(_fd,&_free_blocks_Num,_pointer_size);  //get free blocks number


                int temp = ((3 + _inodes_num + _additional_iNodsb + _additional_Fb)*_block_size);
                int totalBlocks = (_dataDiskSize - temp )/_block_size;


                //check if there are no i nodes to free
                if(_free_blocks_Num == totalBlocks){
                        perror("there are no blocks to free ");
                        return -1;   // -1 = all the blocks are free
                }

                //check if the block is out of bound
                if( (dblock >  (_dataDiskSize - _block_size)) || (dblock < temp) ){
                        perror("block cannot be freed , block out of bound ");
                        return -1;   //
                }



                /////////// UPDATE FREE BLOCKS ADDRESS LIST //////////////



                check = lseek(_fd,20,SEEK_SET); //start of free blocks list
                if(check < 0){
                        perror("seek failed at free block (2) (low level) ");

                }
                int position = 0;
                read(_fd,&position,_pointer_size);// position = (blank)free address in free blocks list
                if(_first_additional_fBlocks !=0){
                if(position == ((_block_size*3) - _pointer_size)){

                        handle_freeBlock(position,dblock);
                        position = _first_additional_fBlocks;
                        write(_fd,&position,_pointer_size);     //update the last free block pointer

                }else if( (((position-_first_additional_fBlocks) +2*_pointer_size )  % _block_size) == 0 ){

                                handle_freeBlock(position,dblock);
                               // position += _pointer_size*2;
                                position += _pointer_size;
                                write(_fd,&position,_pointer_size);     //update the last free block

                }else{

                        handle_freeBlock(position,dblock);
                        position += _pointer_size;
                        write(_fd,&position,_pointer_size);     //update the last free block pointer
                }


                }else{

                                handle_freeBlock(position,dblock);
                                position += _pointer_size;
                                write(_fd,&position,_pointer_size);     //update the last free block pointer
                }


                //////////////////// DELETE DATA /////////////////////////////

                char noVal = 0;
                lseek(_fd,dblock,SEEK_SET);
                for (int i = 0 ; i < _block_size ; i++){
                        write(_fd,&noVal,_char_size);
                }

                int freeBlocks = 0;
                //update free i nodes number
                lseek(_fd,12,SEEK_SET);
                read(_fd,&freeBlocks,_pointer_size);
                freeBlocks++;
                lseek(_fd,12,SEEK_SET);
                write(_fd,&freeBlocks,_pointer_size);

                return 0; // yekshemaj . great success.

}






void LowLevel::handle_freeBlock(int position, int block_address)
{

        lseek(_fd,position,SEEK_SET);  // move fp to position
        write(_fd,&block_address,_pointer_size); //write the address of the free i node to the end of the list
        lseek(_fd,20,SEEK_SET); //**
}


int LowLevel::get_inode_position(int i)
{
        int ans =  (_block_size*3)+ (_block_size*calculate_additional_block()); //start of the i nodes array
        ans = ((i - ans)/_block_size);
        return ans;

}


int LowLevel::calculate_additional_block()
{
        int free_blocks_Num =(int)(((_dataDiskSize/_block_size) - 3) - _inodes_num);
        _additional_Fb = 0;
        _additional_iNodsb = 0;
        int added = 0;
        double additionBlocks;
        additionBlocks = ((double)free_blocks_Num/((double)_block_size/(double)_pointer_size)) - 1;  //calculate additional address blocks
        if (additionBlocks > 0 && additionBlocks < 1 ){                                                                                    //for free blocks
                        added++;
                        _additional_Fb++;
        }else{

                        added = (int)additionBlocks; //additional blocks for free block addresses needed
                        _additional_Fb = (int)additionBlocks;
        }

        additionBlocks = ((double)_inodes_num/((double)_block_size/(double)_pointer_size)) - 1;  //calculate additional address blocks
        if (additionBlocks > 0 && additionBlocks < 1 ){                                                                                   //for free i-nodes
                        added++; //additional block for free i nodes addresses needed
                        _additional_iNodsb++;
        }else{
                                added += (int)additionBlocks; //additional blocks for free i nodes addresses needed
                                _additional_iNodsb = (int)additionBlocks;
        }

        return added;


}

void LowLevel::set_additionals()
{
        int previous =0;
        int val =0;
        int position = 16;

        lseek(_fd,position,SEEK_SET);
        read(_fd,&position,_pointer_size);
        lseek(_fd,position,SEEK_SET);
        position = (_block_size - _pointer_size);

        lseek(_fd,position,SEEK_CUR); //address of last value in the first free blocks list
        read(_fd,&val,_pointer_size);
        position = 0 - _pointer_size*2;
        lseek(_fd,position,SEEK_CUR); //address of second to last value in the first free blocks list
        read(_fd,&previous,_pointer_size);
        if ((val - previous) != _block_size){
                _first_additional_fBlocks = val;
        }

        val = 0;
        previous = 0;
        position = 32;
        lseek(_fd,position,SEEK_SET);
        read(_fd,&position,_pointer_size);
        lseek(_fd,position,SEEK_SET);
        position = (_block_size - _pointer_size);
        lseek(_fd,position,SEEK_CUR); //address of last value in the first free blocks list
        read(_fd,&val,_pointer_size);

        position = 0 - _pointer_size*2;
        lseek(_fd,position,SEEK_CUR); //address of second to last value in the first free blocks list
        read(_fd,&previous,_pointer_size);

        if ((val - previous) != _block_size){
                _first_additional_iNods = val;
        }


}


int LowLevel::get_hardLinks_num(int i_node)
{
          int ans = 0;
          int iNodephys = get_inode_physcal_address(i_node) + 1 ;
          lseek(_fd,iNodephys ,SEEK_SET);
          read(_fd,&ans,_char_size);

          return ans;

}

void LowLevel::set_hardLinks_num(int i_node,int hl_num)
{

  int iNodephys = get_inode_physcal_address(i_node) + _char_size;
  lseek(_fd,iNodephys ,SEEK_SET);
  write(_fd,&hl_num,_pointer_size);

}




//////////////////// Eran  //////////////////////////////////////////////////

char LowLevel::getInodeType(int i_node)
{
  char ans;
  int iNodeAdd = get_inode_physcal_address(i_node);
  int offset = iNodeAdd;
  lseek(_fd,offset,SEEK_SET);
  read(_fd,&ans,_char_size);
  return ans;
}

/*
 *  return the number of the i-th data block containing the data of this file
 */
int LowLevel::getDataBlock(int i_node, int i)
{

  int sizeOfChar = _char_size * 2;
  int iNodephys = get_inode_physcal_address(i_node) + sizeOfChar +_pointer_size;
  int ans = 0;
  if (i < 10 ){
    iNodephys += (_pointer_size * i); //Not indirect : iNodephys + i*po...
    lseek(_fd,iNodephys ,SEEK_SET);
    read(_fd,&ans,_pointer_size);

  }
  else{
          iNodephys += (_pointer_size * 10) ;  //For indirect : iNodephys + 44Byte
          lseek(_fd,iNodephys ,SEEK_SET);
          read(_fd,&ans,_pointer_size);

          if ( ans != 0 ){
          iNodephys = 0;
          iNodephys = (_pointer_size * (i - 10)) + ans;
          lseek(_fd,iNodephys ,SEEK_SET);
          read(_fd,&ans,_pointer_size);

          }
          else{

        	return 0;
          }
  }
  return ans;
}

/*
 *  return the size of the file associated to i-th node
 */
int LowLevel::getFileSize(int i_node)
{
  int sizeOfChar = _char_size * 2;
  int size;
  int iNodeAdd = get_inode_physcal_address(i_node);
  int offset = iNodeAdd + sizeOfChar;
  lseek(_fd,offset ,SEEK_SET);
  read(_fd,&size,_pointer_size);
  return size;
}

int LowLevel::get_inode_physcal_address(int i)
{

				int ans = (_block_size*3) + (i*_block_size);
                ans += (calculate_additional_block()*_block_size);
                return ans;
}

/*
 *  sets the size of the file associated to i-th node to newSize
 */
int LowLevel::setFileSize(int i_node, int newSize)
{
  //TODO : check the directory case
    int sizeOfChar = _char_size * 2;
    int iNodeAdd = get_inode_physcal_address(i_node);
    int offset = iNodeAdd + sizeOfChar;           //I node address + 2Byte
    lseek(_fd,offset ,SEEK_SET);
    int ans = write(_fd,&newSize,_pointer_size);

    return ans;
}

int LowLevel::setInodeType(int i_node, char filetype)
{
    int iNodeAdd = get_inode_physcal_address(i_node);
    int offset = iNodeAdd;
    lseek(_fd,offset,SEEK_SET);
    int ans = write(_fd,&filetype,_char_size);
    if ( ans < 0 ){
      switch (errno) {
           case  EBADF:  printf ("Can't write to i node .\n"); return -1;
           default:     printf ("Unknown error in setInodeType.\n"); return -1;
         }
      }

    return ans;
}

/*
 *  read the entire content of the dblockNum's and copy it into buf.
 *  Assuming dblockNum is a physical address
 */
int LowLevel::readBlock(int dblockNum, char *buf)
{
  lseek(_fd,dblockNum,SEEK_SET);
  int ans = read(_fd,buf,_block_size);
  if ( ans < 0 ){
    switch (errno) {
         case  EBADF:  printf ("Can't read from block .\n"); return -1;
         default:     printf ("Unknown error in readBlock.\n"); return -1;
       }
    }
  return ans;

}

/*
 * overwrite the content of block with number dblockNum with
 *  the newdata block. Assume newdata's length is equal to dataBlockSize.
 */
int LowLevel::writeBlock(int dblockNum, char *newdata)
{
  lseek(_fd,dblockNum,SEEK_SET);
  int ans = write(_fd,newdata,_block_size);
  if ( ans < 0 ){
  switch (errno) {
       case  EBADF:  printf ("Can't write to block .\n"); return -1;
       default:     printf ("Unknown error in writeBlock.\n"); return -1;
     }
  }

  return ans;
}
/*
 * sets the i-th data block of this file to be dblockNum.
 */
int LowLevel::setDataBlock(int i_node, int i, int dblockNum)
{
    int sizeOfChar = _char_size * 2;
    int iNodephys = get_inode_physcal_address(i_node) + sizeOfChar +_pointer_size;
    int ans = 0;
    if (i < 10 ){
      iNodephys += (_pointer_size * i); //Not indirect : iNodephys + i*po...
      lseek(_fd,iNodephys ,SEEK_SET);
      write(_fd,&dblockNum,_pointer_size);
    }
    else{
            iNodephys += (_pointer_size * 10) ;  //For indirect : iNodephys + 44Byte
            lseek(_fd,iNodephys ,SEEK_SET);
            read(_fd,&ans,_pointer_size);

              if ( ans == 0){                     //In case the indirect hadn't been used yet
            	  ans = dblockNum;


            	lseek(_fd,iNodephys ,SEEK_SET);
                write(_fd,&ans,_pointer_size);
              }

            iNodephys = 0;
            ans = (_pointer_size * (i - 10)) + ans;

            int dblockNum2 = allocateDataBlock();

            lseek(_fd,ans ,SEEK_SET);
		    write(_fd,&dblockNum2,_pointer_size);
    }


    return ans;
}




int LowLevel::write_dir(int dblockNum, int i_node, char *dirName,int position)
{


/*      if ( (_block_size - position) < 16 ){
                perror("not enough space to write dir in a data block");
                return -1;
        }

*/

        lseek(_fd,dblockNum+position,SEEK_SET);
        write(_fd,&i_node,_pointer_size);


        write(_fd,dirName,_pointer_size*3);

        return 0;

}


dir* LowLevel::read_dir(int dblockNum, int i_node, int position)
{

        int iNode = 0;
        char* file_name = new char[12];

        for(int i=0 ; i< 12 ;i++){
                file_name[i] = 0;
        }
        dir* tDir = new dir();
        lseek(_fd,dblockNum+position,SEEK_SET);
        read(_fd,&iNode,_pointer_size);
        lseek(_fd,dblockNum+position+4,SEEK_SET);
        read(_fd,file_name,12);

        tDir->_iNode = iNode;
        tDir->_file_name = file_name;


        return tDir;

}



int LowLevel::get_root_iNode()
{
        int root = 0;
        lseek(_fd,8,SEEK_SET);
        read(_fd,&root,_pointer_size);
        return root;

}

void LowLevel::set_root_iNode(int i_node)
{

        lseek(_fd,8,SEEK_SET);
        write(_fd,&i_node,_pointer_size);
}

int LowLevel::check_space()
{

   int freeBlocks = 0;
   lseek(_fd,12,SEEK_SET);
   read(_fd,&freeBlocks,_pointer_size);
   if(freeBlocks <= 0){
	   perror("not enough space. no available free blocks");
	   return -1;
   }

   int freeInods = 0;
   lseek(_fd,28,SEEK_SET);
   read(_fd,&freeInods,_pointer_size);
   if(freeInods <= 0){
  	   perror("not enough space. no available free i nodes");
  	   return -1;
    }

   return 0;


}






/*int checkForError(int check ){
if (check <= 0){
                  perror ("Open FILE_SYS failed in Low level.\n");
                    switch (errno) {
                      case EACCES:  perror ("Permission denied.\n"); return -1;
                      case EMFILE:  perror ("No file handle available.\n"); return -1;
                      case ENOENT:  perror ("File or path not found.\n"); return -1;
                      default:      perror ("Unknown error.\n"); return -1;
                    }
        }

return 1;
}*/




































